using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using System.Drawing;

[assembly: WebResource("CodeStory.WebControls.Resource.PropertyGrid.js", "application/x-javascript")]
[assembly: WebResource("CodeStory.WebControls.Resource.PropertyGrid_ExpandImage.jpg", "image/jpg")]
[assembly: WebResource("CodeStory.WebControls.Resource.PropertyGrid_CollapseImage.jpg", "image/jpg")]

namespace CodeStory.WebControls
{
	[ToolboxData("<{0}:PropertyGrid runat=server></{0}:PropertyGrid>")]
	public class PropertyGrid : WebControl, INamingContainer
	{
		private object selectedObject = null;

		private PropertyGridRowCollection _rowsCollection;
		private ArrayList _rowsArray;
		private bool _requiresDataBinding;
		private bool _preRendered;
		string _expandImage;
		string _collapseImage;
		private PropertyGridItem rootGridItem;
		#region Appearence Properties

		[DefaultValue(20)]
		[Bindable(true)]
		[Category("Apperance")]
		public int ItemIndent {
			get {
				object obj = ViewState["ItemIndent"];
				return obj == null ? 20 : (int)obj;
			}
			set { ViewState["ItemIndent"] = value; }
		}

		public string ExpandImage {
			get {
				if (string.IsNullOrEmpty(_expandImage)) {
					object obj = ViewState["ExpandImage"];
					if (obj != null) {
						_expandImage = (string)obj;
					}
					if (string.IsNullOrEmpty(_expandImage)) {
						if (Page != null) {
							_expandImage = Page.ClientScript.GetWebResourceUrl(GetType(), "CodeStory.WebControls.Resource.PropertyGrid_ExpandImage.jpg");
						}
					}
				}
				return _expandImage;
			}
			set { ViewState["ExpandImage"] = value; }
		}
		public string CollapseImage {
			get {
				if (string.IsNullOrEmpty(_collapseImage)) {
					object obj = ViewState["CollapseImage"];
					if (obj != null) {
						_collapseImage = (string)obj;
					}
					if (string.IsNullOrEmpty(_collapseImage)) {
						if (Page != null) {
							_collapseImage = Page.ClientScript.GetWebResourceUrl(GetType(), "CodeStory.WebControls.Resource.PropertyGrid_CollapseImage.jpg");
						}
					}
				}
				return _collapseImage;
			}
			set { ViewState["CollapseImage"] = value; }
		}

		[Browsable(false)]
		public object SelectedObject {
			get {
				if (selectedObject == null) {
					if (SelectedObjectType == null) {
						return null;
					}
					try {
						selectedObject = Activator.CreateInstance(SelectedObjectType);
					} catch {
					}
				}
				OnBeforeGenereateObject(selectedObject); 
				if (selectedObject == null) {
					return null;
				}
				EnsureChildControls();
				GenerateObject();
				return selectedObject;
			}
			set {
				if (value == null) {
					return;
				}
				if (ReferenceEquals(selectedObject, value)) {
					return;
				}
				selectedObject = value;
				SelectedObjectType = selectedObject.GetType();
			}
		}

		#endregion
		#region Properties

		private Type SelectedObjectType {
			get {
				object obj = ViewState["SelectedObjectType"];
				return obj == null ? null : (Type)obj;
			}
			set { ViewState["SelectedObjectType"] = value; }
		}

		public PropertyGridItemCollection PropertyItems {
			get {
				return RootGridItem.PropertyItems;
			}
		}
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual PropertyGridRowCollection Rows {
			get {
				if (this._rowsCollection == null) {
					if (this._rowsArray == null) {
						this.EnsureChildControls();
					}
					if (this._rowsArray == null) {
						this._rowsArray = new ArrayList();
					}
					this._rowsCollection = new PropertyGridRowCollection(this._rowsArray);
				}
				return this._rowsCollection;
			}
		}
		protected bool RequiresDataBinding {
			get { return this._requiresDataBinding; }
			set {
				if (((value && this._preRendered) && ((this.Page != null))) && !this.Page.IsCallback) {
					this._requiresDataBinding = true;
					this.EnsureDataBound();
				} else {
					this._requiresDataBinding = value;
				}
			}
		}
		internal PropertyGridItem RootGridItem {
			get {
				if (rootGridItem == null) {
					rootGridItem = new PropertyGridItem(this, true);
					if (IsTrackingViewState) {
						((IStateManager)rootGridItem).TrackViewState();
					}
				}
				return rootGridItem;
			}
		} 
		#endregion

		private void EnsureDataBound() {
			if (this.RequiresDataBinding) {
				this.DataBind();
			}
		}

		private void GenerateObject() {
			Dictionary<string, object> dict = new Dictionary<string, object>();
			foreach (PropertyGridRow row in Rows) {
				if (row.PropertyEditor != null) {
					object val = row.PropertyEditor.GetValue();
					dict[row.PropertyGridItem.InternalValuePath] = val;
				}
			}
			PropertyDescriptorCollection props = TypeDescriptor.GetProperties(selectedObject);
			SetObjectValue(selectedObject,dict, props,"");
		}

		private void SetObjectValue(object valueObject,Dictionary<string, object> dict, PropertyDescriptorCollection props,string valuepath) {
			foreach (PropertyDescriptor descriptor in props) {
				string name = Escape(descriptor.Name);
				string currentValuePath = name;
				if (!string.IsNullOrEmpty(valuepath)) {
					currentValuePath = string.Format("{0}\\{1}", valuepath, name);
				}
				if (dict.ContainsKey(currentValuePath)) {
					object val = dict[currentValuePath];
					TypeConverter con = descriptor.Converter;
					if (con.CanConvertFrom(typeof(string))) {
						descriptor.SetValue(valueObject, con.ConvertFromString(val.ToString()));
					}
				}
				object propertyValue = null;
				try {
					propertyValue = descriptor.GetValue(valueObject);
				} catch {
				}
				if (descriptor.Converter.GetPropertiesSupported()) {
					PropertyDescriptorCollection subProps = descriptor.Converter.GetProperties(propertyValue);
					SetObjectValue(propertyValue, dict, subProps, currentValuePath);
				}
			}
		}

		public override void DataBind() {
			base.DataBind();
			PerformDataBound();
		}

		protected override void CreateChildControls() {
			if (this.ViewState["_!ItemCount"] == null) {
				if (this.RequiresDataBinding) {
					this.PerformDataBound();
				}
			} else {
				this.CreateControlHierarchy(false);
				base.ClearChildViewState();
			}
		}

		private void CreateControlHierarchy(bool databound) {
			Controls.Clear();
			ChildControlsCreated = true;

			if (this._rowsArray != null) {
				this._rowsArray.Clear();
			} else {
				this._rowsArray = new ArrayList();
			}

			Table table = new Table();
			Controls.Add(table);
			table.ID = this.ID;
			PropertyGridItemCollection items = PropertyItems;
			CreateRows(table, items, databound);
			if (databound) {
				this.ViewState["_!ItemCount"] = PropertyItems.Count;
			}
		}

		private void CreateRows(Table table, PropertyGridItemCollection items, bool databound) {
			foreach (PropertyGridItem item in items) {
				PropertyGridRow row = new PropertyGridRow();
				table.Rows.Add(row);
				_rowsArray.Add(row);
				row.PropertyGridItem = item;

				TableCell cell;
				//Field name cell
				cell = CreateTableCell();
				row.Cells.Add(cell);
				if (item.HasPropertyItems) {
					System.Web.UI.WebControls.Image img = new System.Web.UI.WebControls.Image();
					cell.Controls.Add(img);
					img.ImageUrl = ExpandImage;
					img.Attributes.Add("onclick", "javascript:ToggleRowState(this);");
				}
				cell.Style.Add(HtmlTextWriterStyle.PaddingLeft, Unit.Pixel(item.Depth * ItemIndent).ToString());
				Label lblName = new Label();
				lblName.Text = item.DisplayName;
				cell.Controls.Add(lblName);
				Label lblDescription = new Label();
				lblDescription.Text = item.Description;
				cell.Controls.Add(lblDescription);
				//Control
				cell = CreateTableCell();
				row.Cells.Add(cell);
				try {
					object obj = Activator.CreateInstance(item.EditorType);
					if (obj is Control) {
						Control ctrl = (Control)obj;
						cell.Controls.Add(ctrl);
						
						IPropertyEditor editor = ctrl as IPropertyEditor;
						if (editor != null) {
							row.PropertyEditor = editor;
							editor.IsReadOnly = item.IsReadOnly;
							if (databound) {
								if (item.StandardValuesSupported) {
									editor.SetStandardValues(item.StandardValueString);
								}
								if (item.CustomEditor) {
									editor.Prepare();
								}
								editor.SetValue(item.PropertyValue);
							}
						}
					}
				} catch {
				}
				if (item.HasPropertyItems) {
					row.HasChildRows = true;
					CreateRows(table, item.PropertyItems, databound);
				}
			}
		}

		private TableCell CreateTableCell() {
			TableCell cell = new TableCell();
			cell.BackColor = Color.White;
			return cell;
		}

		private void PerformDataBound() {
			if (selectedObject == null) {
				return;
			}
			PropertyItems.Clear();
			PropertyDescriptorCollection props = TypeDescriptor.GetProperties(selectedObject);
			CreatePropertyGridItems(props,PropertyItems,selectedObject);
			this.RequiresDataBinding = false;
			CreateControlHierarchy(true);
		}

		private void CreatePropertyGridItems(PropertyDescriptorCollection props,PropertyGridItemCollection items,object valueObject) {
			props.Sort();
			foreach (PropertyDescriptor descriptor in props) {
				PropertyGridCreateItemEventArgs e = new PropertyGridCreateItemEventArgs(descriptor);
				OnCreatingItem(e);
				if (e.Cancel) continue;
				PropertyGridItem item = new PropertyGridItem(this,false);
				item.Name = descriptor.Name;
				item.DisplayName = descriptor.DisplayName;
				object propertyValue = null;
				try {
					propertyValue = descriptor.GetValue(valueObject);
				} catch {
				}
				if (propertyValue != null) {
					string valueString;
					if (descriptor.Converter.CanConvertTo(typeof(string))) {
						valueString = descriptor.Converter.ConvertToString(propertyValue);
					} else {
						valueString = propertyValue.ToString();
					}
					item.PropertyValue = valueString;
				}

				item.Description = descriptor.Description;
				SetEditorType(descriptor,item);
				item.IsReadOnly = descriptor.IsReadOnly;
				item.StandardValuesSupported = descriptor.Converter.GetStandardValuesSupported();
				if (item.StandardValuesSupported) {
					List<string> list = new List<string>();
					foreach (object obj in descriptor.Converter.GetStandardValues()) {
						string str = descriptor.Converter.ConvertToString(obj);
						list.Add(str);
					}
					item.StandardValueString = string.Join("$", list.ToArray());
					item.StandardValuesExclusive = descriptor.Converter.GetStandardValuesExclusive();
				}
				items.Add(item);

				if (descriptor.Converter.GetPropertiesSupported()) {
					PropertyDescriptorCollection subProps = descriptor.Converter.GetProperties(propertyValue);
					CreatePropertyGridItems(subProps,item.PropertyItems,propertyValue);
				}
			}
		}

		private void SetEditorType(PropertyDescriptor descriptor, PropertyGridItem item) {
			PropertyEditorAttribute editor = descriptor.Attributes[typeof(PropertyEditorAttribute)] as PropertyEditorAttribute;
			if (editor != null) {
				item.EditorType = editor.EditorType;
				item.CustomEditor = true;
				return;
			}
			//Type type = descriptor.PropertyType;
			if (descriptor.Converter.GetStandardValuesSupported()) {
				item.EditorType = typeof(DropDownPropertyEditor);
				return;
			}
			item.EditorType = typeof(TextBoxPropertyEditor);
		}

		protected override void OnInit(EventArgs e) {
			base.OnInit(e);
			if (this.Page != null) {
				if (!base.IsViewStateEnabled && this.Page.IsPostBack) {
					this.RequiresDataBinding = true;
				}
			}
		}

		

		protected override void OnPreRender(EventArgs e) {
			this._preRendered = true;
			this.EnsureDataBound();
			string script = @"var PG = {
PlusImage:"""+ ExpandImage +@""",
MinusImage:"""+ CollapseImage +@"""
};";

			Page.ClientScript.RegisterClientScriptBlock(GetType(), "PropertyGrid", script, true);

			Page.ClientScript.RegisterClientScriptResource(typeof(DataSourceHelper), "CodeStory.WebControls.Resource.CodeStory.js");
			Page.ClientScript.RegisterClientScriptResource(GetType(), "CodeStory.WebControls.Resource.PropertyGrid.js");
			base.OnPreRender(e);
		}

		protected override void Render(HtmlTextWriter writer) {
			if (this.Page != null) {
				this.Page.VerifyRenderingInServerForm(this);
			}
			this.PrepareControlHierarchy();
			this.RenderContents(writer);
		}

		private void PrepareControlHierarchy() {
			if (Controls.Count == 0) {
				return;
			}
			Table table = (Table)this.Controls[0];
			table.CopyBaseAttributes(this);
			if (ControlStyleCreated && !ControlStyle.IsEmpty) {
				table.ApplyStyle(ControlStyle);
			} else {
				table.GridLines = GridLines.Both;
				table.CellPadding = 0;
				table.CellSpacing = 0;
			}
		}

		protected override void LoadViewState(object savedState) {
			if (savedState == null) {
				return;
			}
			object[] state = (object[])savedState;
			base.LoadViewState(state[0]);
			((IStateManager)RootGridItem).LoadViewState(state[1]);
		}

		protected override object SaveViewState() {
			object[] state = new object[2];
			state[0] = base.SaveViewState();
			if (rootGridItem != null) {
				state[1] = ((IStateManager)rootGridItem).SaveViewState();
			}
			if (state[0] != null || state[1] != null) {
				return state;
			}
			return null;
		}

		protected override void TrackViewState() {
			if (rootGridItem != null) {
				((IStateManager)rootGridItem).TrackViewState();
			}
			base.TrackViewState();
		}
		#region Events
		private static readonly object BeforeGenereateObjectEventObject = new object();
		public event PropertyGridEventHandler BeforeGenereateObject {
			add { Events.AddHandler(BeforeGenereateObjectEventObject, value); }
			remove { Events.RemoveHandler(BeforeGenereateObjectEventObject,value); }
		}
		protected void OnBeforeGenereateObject(object obj) {
			PropertyGridEventHandler handler = (PropertyGridEventHandler)Events[BeforeGenereateObjectEventObject];
			if (handler != null) {
				PropertyGridEventArgs e = new PropertyGridEventArgs(obj);
				handler(this,e);
			}
		}
		private static readonly object CreatingItemEventObject = new object();
		public event PropertyGridCreateItemEventHandler CreatingItem {
			add { Events.AddHandler(CreatingItemEventObject, value); }
			remove { Events.RemoveHandler(CreatingItemEventObject, value); }
		}
		protected void OnCreatingItem(PropertyGridCreateItemEventArgs e) {
			PropertyGridCreateItemEventHandler handler = (PropertyGridCreateItemEventHandler)Events[CreatingItemEventObject];
			if (handler != null) {
				handler(this, e);
			}
		}
		#endregion

		public PropertyGridItem CreateItem() {
			return  new PropertyGridItem(this, false);
		}

		internal static string Escape(string value) {
			StringBuilder builder = null;
			if (string.IsNullOrEmpty(value)) {
				return string.Empty;
			}
			int startIndex = 0;
			int count = 0;
			for (int i = 0; i < value.Length; i++) {
				switch (value[i]) {
					case '\\':
						if (builder == null) {
							builder = new StringBuilder(value.Length + 5);
						}
						if (count > 0) {
							builder.Append(value, startIndex, count);
						}
						builder.Append("*|*");
						startIndex = i + 1;
						count = 0;
						break;
					case '|':
						if (builder == null) {
							builder = new StringBuilder(value.Length + 5);
						}
						if (count > 0) {
							builder.Append(value, startIndex, count);
						}
						builder.Append("||");
						startIndex = i + 1;
						count = 0;
						break;
					default:
						count++;
						break;
				}
			}
			if (builder == null) {
				return value;
			}
			if (count > 0) {
				builder.Append(value, startIndex, count);
			}
			return builder.ToString();
		}
	}
}